using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using Drifting.Graphics.PhysicalObjects;
using Drifting.Graphics.EffectObjects;
using Drifting.Foundation;
using Drifting.MotionPlanning.Systems;
using Drifting.MotionPlanning.SingleThreading;
using Drifting.MotionPlanning.SingleThreading.Algorithms;
using Drifting.MotionPlanning.SingleThreading.Planners;
using Drifting.MotionPlanning.MultiThreading.Planners;
using Drifting.MotionPlanning.Primitives;
using Drifting.MotionPlanning.PointSets;
using Drifting.Graphics.Visualizers;
using Drifting.Graphics.PhysicalObjects.Vehicles;
using Drifting.Scenes;
using Drifting.Graphics;

namespace Drifting
{

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        RasterizerState rasterizerState;
        GraphicsDeviceManager graphics; 
        SpriteBatch spriteBatch;

        Matrix viewMatrix;
        Matrix projectionMatrix;

        int screen_width;
        int screen_height;

        long n_draws = 0;
        double gameTime_draw_last;
        private double fps = 0;
                
        bool DrawingEnabled = true;

        Camera camera;
        Console console;

        Scene scene;
        
        Planner planner;

        //

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferMultiSampling = true;

            graphics.PreferredBackBufferHeight = 701;
            graphics.PreferredBackBufferWidth = 1201;

        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            this.IsMouseVisible = true;
            this.Exiting += new EventHandler<EventArgs>(ExitKinodynamicLab);

            rasterizerState = new RasterizerState();
            rasterizerState.CullMode = CullMode.None;

            graphics.GraphicsDevice.BlendState = BlendState.AlphaBlend;
            graphics.GraphicsDevice.RasterizerState = rasterizerState;

            graphics.ApplyChanges();

            screen_width = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
            screen_height = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;

            camera = new Camera(graphics.GraphicsDevice);
   
            //test planner

            BaseSystem point3D = new Point3D();
            BaseSystem point2D = new Point2D();
            BaseSystem OneLink = new OneLink();
            BaseSystem rallyCar = new RallyCar();

            Skin_OneLink onelink_visual = new Skin_OneLink(new List<State>(), GraphicsDevice);
            Skin_G point_visual = new Skin_G(new List<State>(), GraphicsDevice);

            base.Initialize();
        }
        
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            SpriteFont Font = Content.Load<SpriteFont>("Segoe UI Mono");
            
            // TODO: use this.Content to load your game content here
            console = new Console(spriteBatch, Font);
            console.View = camera;
            console.Application = this;
            console.Planner = planner;
            console.Scene = scene;

            //scene = new Basic(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new ModelCalibration(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new Introduction(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);

            //scene = new SST_multithreaded(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new SST_Drift8D(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new CollisionTest(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new Comparison_SST(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new Comparison_RRTstar(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new Comparison_iSST(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);

            //scene = new FlightTest(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new ISSCalibration(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);
            //scene = new SST_Drift12D(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this);

            //scene = new BiPedal_Test(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this, Font);
            //scene = new BiPedal_plan(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this, Font);
            scene = new DireWolf_prototype(ref viewMatrix, ref projectionMatrix, GraphicsDevice, camera, this, Font);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            console.Update(ref viewMatrix, ref projectionMatrix, gameTime);

            scene.Update();

            base.Update(gameTime);
        }
        
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            fps = 1 / (gameTime.TotalGameTime.TotalSeconds - gameTime_draw_last);
            gameTime_draw_last = gameTime.TotalGameTime.TotalSeconds;

            if (DrawingEnabled == false) return;

            Color backgroundColor = Color.LightGray;
            if (scene != null)
                backgroundColor = scene.BackgroundColor();

            GraphicsDevice.Clear(backgroundColor);
            ResetRenderingState();

            if (scene != null)
                scene.Draw(viewMatrix, projectionMatrix);
                        
            console.FPS = fps;
            console.Draw();

            n_draws++;
            base.Draw(gameTime);
        }
        
        /// <summary>
        /// This function needs to be called before drawing the scene
        /// </summary>
        public void ResetRenderingState()
        {
            GraphicsDevice.RasterizerState = rasterizerState;
            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
        }

        protected void ExitKinodynamicLab(Object sender, EventArgs args)
        {
            if (scene != null)
                scene.Terminate();
        }

        
    }
}
